#include <imbus.h>
#include <stdio.h>
#include <string.h>
#include <math.h>

#include "im_internals.h"

typedef struct
{
    IMUInt64 first_time;
    IMUInt64 last_time;
    IMInt timeout;
    IMInt times;
    IMDouble delta_sum;
} TimeoutCallbackData;

void free_timeout_callback_data (IMPointer data)
{
    TimeoutCallbackData *d = (TimeoutCallbackData*) data;
    IMDouble average_timeout = (IMDouble)(d->last_time - d->first_time)/(IMDouble)(d->times);

    printf ("Free timeout=%5d: average=%7.2f, avg_delta=%f%%, times=%d\n",
            d->timeout,
            average_timeout,
            sqrt (d->delta_sum / (IMDouble)d->times) * 100.0 / average_timeout,
            d->times);

    im_slice_delete(TimeoutCallbackData, d);
}

IMBool timeout_callback (IMMainLoop *loop, IMInt watch_id, IMPointer data)
{
    IMUInt64 now = im_get_current_time_in_milliseconds();
    IMDouble average_timeout;
    IMInt this_time;
    TimeoutCallbackData *d = (TimeoutCallbackData*) data;

    if (d->last_time == 0) {
        d->last_time = now;
        d->first_time = now;
        printf ("timeout: now=%lld, first time, preset=%d\n",
                now, d->timeout);
        return TRUE;
    }

    this_time = now - d->last_time;
    d->delta_sum += (IMDouble)((this_time - d->timeout)*(this_time - d->timeout));
    d->times++;
    average_timeout = (IMDouble)(now - d->first_time)/(IMDouble)(d->times);

    printf ("id=%d timeout=%5d: average=%7.2f, this=%5d, avg_delta=%f%%, times=%d\n",
            watch_id,
            d->timeout,
            average_timeout,
            this_time,
            sqrt (d->delta_sum / (IMDouble)d->times) * 100.0 / average_timeout,
            d->times);

    d->last_time = now;

    return TRUE;
}

IMBool io_read_callback (IMMainLoop *loop, IMInt watch_id, IMPointer data)
{
    IMIOChannel *channel = (IMIOChannel *)data;
    fprintf (stderr, "io_read_callback id=%d, fd=%d\n",
             watch_id, im_io_channel_unix_get_fd (channel));
    char buf [128];
    IMSize bytes_read;
    IMIOError error;
    im_io_channel_read (channel, buf, 128, &bytes_read, &error);
    buf [bytes_read] = 0;
    if (bytes_read && buf [bytes_read-1] == '\n')
        buf [bytes_read-1] = 0;
    printf ("%s\n", buf);
    if (strncmp (buf, "quit", 4) == 0) {
        im_main_loop_quit (loop);
    } else if (strncmp (buf, "run", 3) == 0) {
        im_main_loop_run (loop);
    } else if (strncmp (buf, "timeout", 7) == 0) {
        IMInt id;
        TimeoutCallbackData *data = im_slice_new (TimeoutCallbackData);
        data->first_time = 0;
        data->last_time = 0;
        data->times = 0;
        data->delta_sum = 0;
        data->timeout = (random () % 10000);

        if (strlen (buf) > 8)
            data->timeout = strtol(buf + 8, 0, 10);

        id = im_main_loop_add_timeout_watch (loop, data->timeout,
                                             timeout_callback, data,
                                             free_timeout_callback_data);
        if (id >= 0)
            fprintf (stderr, "Timeout watch added, timeout=%d, id=%d\n",
                    data->timeout, id);
        else
           fprintf (stderr, "Failed to add timeout watch, timeout=%d.\n",
                    data->timeout);
    } else if (strncmp (buf, "io", 2) == 0 && strlen (buf) > 3) {
        IMIOChannel *channel =
            im_io_channel_new_file (buf + 3, "r+", 0);
        if (channel) {
            IMInt id = im_main_loop_add_io_read_watch (loop, channel,
                                                       io_read_callback,
                                                       channel,
                                                       0);
            if (id >= 0)
                fprintf (stderr, "IO watch added, file=%s, id=%d\n", buf+3, id);
            else
                fprintf (stderr, "Failed to add IO watch, file=%s.\n", buf+3);
            im_object_unref (channel);
        } else {
            fprintf (stderr, "Failed to create io channel for file %s.\n", buf+3);
        }
    } else if (strncmp (buf, "remove", 6) == 0 && strlen (buf) > 7) {
        IMInt id = strtol (buf + 7, 0, 10);
        if (id >= 0) {
            if (im_main_loop_remove_watch (loop, id))
                fprintf (stderr, "Watch %d was removed.\n", id);
            else
                fprintf (stderr, "Failed to remove watch %d.\n", id);
        }
    } else if (strncmp (buf, "iteration", 9) == 0) {
        IMBool block = FALSE;
        if (strlen (buf) > 10 && buf[10] == '1')
            block = TRUE;
        im_main_loop_iteration (loop, block);
    }
    return TRUE;
}

int main (int argc, char *argv [])
{
    IMMainLoop *loop;
    IMInt i;
    IMInt id;

    im_init (&argc, &argv);

#if IM_ENABLE_DEBUG
    _im_slice_print_debug_info();
#endif

    loop = im_main_loop_unix_new ();

    if (argc > 1) {
        IMIOChannel *channel;
        for ( i = 1; i < argc; ++i) {
            channel = im_io_channel_new_file (argv[i], "r+", 0);
            if (channel) {
                id = im_main_loop_add_io_read_watch (loop, channel, io_read_callback, channel, 0);
                im_object_unref (channel);
                if (id >= 0)
                    printf ("Watch for %s was added successfully, id is %d\n",
                            argv[i], id);
            }
        }
    }
    im_main_loop_run (loop);
    im_object_unref (loop);

#if IM_ENABLE_DEBUG
    _im_slice_print_debug_info();
#endif
}
/*
vi:ts=4:nowrap:ai:expandtab
*/
